#include "wave.h"

#include <QFile>
#include <QFileInfo>
#include <QByteArray>
#include <QString>
#include <QStringList>

WaveInfo::WaveInfo()
{

}

WaveInfo* WaveInfo::instance()
{
    static WaveInfo ins;

    return &ins;
}

bool WaveInfo::check_wavehead(WAVE_HEAD_S &head, char* tmpBuf, uint32_t filesize)
{
    //RIFF chunk region 12byte
    if(memcmp(&tmpBuf[0], "RIFF", 4) != 0) return false;
    memcpy(&head.RIFFChunkID, tmpBuf, 4);
    memcpy(&head.RIFFChunkSize, &tmpBuf[4], 4);
    if(memcmp(&tmpBuf[8], "WAVE", 4) != 0) return false;
    memcpy(&head.WAVEID, &tmpBuf[8], 4);

    //FMT chunk region 24byte
    memcpy(&head.fmtXChunkID, &tmpBuf[12], 4);
    if(memcmp(&tmpBuf[12], "fmt", 3) != 0) return false;
    memcpy(&head.fmtXChunkSize, &tmpBuf[16], 4);
    memcpy(&head.FormatTag, &tmpBuf[20], 2);
    memcpy(&head.NumChannels, &tmpBuf[22], 2);
    memcpy(&head.SampleRate, &tmpBuf[24], 4);
    memcpy(&head.ByteRate, &tmpBuf[28], 4);
    memcpy(&head.BlockAlign, &tmpBuf[32], 2);
    memcpy(&head.BitsPerSample, &tmpBuf[34], 2);

    if(head.FormatTag != 0x01) return false;

    uint32_t index = 36;
    if(head.fmtXChunkSize > 16) index += (head.fmtXChunkSize - 16);
    if(head.fmtXChunkSize < 16) return false;

    //FACT chunk
    if(memcmp(&tmpBuf[index], "fact", 4) == 0) {
        memcpy(&head.factChunkID, &tmpBuf[index], 4);
        index += 4;
        memcpy(&head.factChunkSize, &tmpBuf[index], 4);
        index += 4;

        index += head.factChunkSize;
    }

    //DATA chunk
    if(index > 504) return false;
    if(memcmp(&tmpBuf[index], "data", 4) != 0) return false;

    memcpy(&head.dataChunkID, &tmpBuf[index], 4);
    index += 4;
    memcpy(&head.dataChunkSize, &tmpBuf[index], 4);
    index += 4;

    head.head_size = index;
    head.audio_size = filesize - index;

    return true;
}

bool WaveInfo::check_waveinfo(const QString& path, bool showall)
{
    if(path.isEmpty()) return false;

    QFile file(path);

    if(!file.open(QIODevice::ReadOnly)) return false;

    QDataStream out(&file);
    QFileInfo fileinfo = QFileInfo(path);
    emit signalWaveInfo(fileinfo.fileName());

    char tmpBuf[512] = {0};
    out.readRawData(tmpBuf, 512);
    uint32_t filesize = file.size(), audio_sz;
    bool ret = true;
    WAVE_HEAD_S head;
    QString info;
    float fsec;
    int sec, min, msec;

    if(filesize < 52) {
        ret = false;
        goto endcheck_waveinfo;
    }

    memset(&head, 0, sizeof(head));
    if(!check_wavehead(head, tmpBuf, filesize)) {
        emit signalWaveInfo(QStringLiteral("ERROR: 音频文件格式异常!"));
        ret = false;
        goto endcheck_waveinfo;
    }

    if(filesize < head.head_size || head.dataChunkSize < 8 || head.head_size > 512) {
        emit signalWaveInfo(QStringLiteral("ERROR: 音频文件大小异常!"));
        ret = false;
        goto endcheck_waveinfo;
    }

    if(showall) {
        info = QString("\t%1:%2").arg(QStringLiteral("帧头长度")).arg(head.head_size);
        emit signalWaveInfo(info);
        info = QString("\tRIFF\n\tRIFFChunkSize:%1\n\tWAVE\n\tfmt\n\tfmtChunkSize:%2\n\tFmtTag:%3\n\tByteRate:%4\n\tBlockAlign:%5")
                   .arg(head.RIFFChunkSize)
                   .arg(head.fmtXChunkSize)
                   .arg(head.FormatTag)
                   .arg(head.ByteRate)
                   .arg(head.BlockAlign);
        emit signalWaveInfo(info);
    }

    audio_sz = filesize - head.head_size;
    info = QString("\t%1%2%3%4%5%6").arg(QStringLiteral("采样率:")).arg(head.SampleRate)
                                .arg(QStringLiteral(" 声道:")).arg(head.NumChannels)
                                .arg(QStringLiteral(" 位深:")).arg(head.BitsPerSample);
    emit signalWaveInfo(info);

    info = QString("\t%1:%2").arg(QStringLiteral("音频长度字节")).arg(audio_sz);
    emit signalWaveInfo(info);

    fsec = head.NumChannels * head.SampleRate * head.BitsPerSample / 8;
    fsec = audio_sz * 1000.0f / fsec;
    msec = (int)fsec;
    sec  = msec / 1000;
    msec = msec - sec * 1000;
    min = sec / 60;
    sec = sec - min * 60;
    info = QString("\t%1%2%3%4%5%7%8").arg(QStringLiteral("音频时长:"))
        .arg(min).arg(QStringLiteral("分"))
        .arg(sec).arg(QStringLiteral("秒"))
        .arg(msec).arg(QStringLiteral("毫秒"));
    emit signalWaveInfo(info);

    if((head.RIFFChunkSize + 8 != head.dataChunkSize + head.head_size)
        || filesize != head.RIFFChunkSize + 8) {
        emit signalWaveInfo(QStringLiteral("WARNING: 音频长度字节异常!"));
        info = QString("\tRIFFChunkSize:%1,dataChunkSize:%2,fileSize:%3").arg(head.RIFFChunkSize).arg(head.dataChunkSize).arg(filesize);
        emit signalWaveInfo(info);
    }

endcheck_waveinfo:
    file.close();

    return ret;
}

void WaveInfo::merge_wave_item(const QString &name)
{
    static uint32_t wave_ch = 0, wave_byte, wave_sample = 0;

    QFile file(name);
    if(file.open(QIODevice::ReadOnly)) {
        QDataStream out(&file);
        WAVE_HEAD_S head;
        char tmpBuf[1024];
        memset(&head, 0, sizeof(head));
        int sz = out.readRawData(tmpBuf, 512);

        if(check_wavehead(head, tmpBuf, file.size())) {
            QFileInfo fileinfo(name);
            QString info = QString("merge %1").arg(fileinfo.fileName());
            emit signalWaveInfo(info);

            if(!_merge_exec) {
                _audio_size = head.audio_size;
                wave_ch     = head.NumChannels;
                wave_byte   = head.BitsPerSample;
                wave_sample = head.SampleRate;

                _mfile->write(tmpBuf, sz);
                memcpy(_wav_head, tmpBuf, head.head_size);
                _head_size = head.head_size;
            }
            else {
                if(head.head_size < 512) _mfile->write(tmpBuf + head.head_size, sz - head.head_size);
                _audio_size += head.audio_size;
            }
            _merge_exec = true;

            if(wave_ch == head.NumChannels && wave_byte == head.BitsPerSample && wave_sample == head.SampleRate) {
                while(!out.atEnd())
                {
                    sz = out.readRawData(tmpBuf, 1024);
                    _mfile->write(tmpBuf, sz);
                }
            }
        }

        file.close();
    }
}

void WaveInfo::merge_wave(const QString& path, const QStringList& wave_list)
{
    uint32_t count = wave_list.count();
    if(count <= 1 || path.isEmpty()) return;

    _mfile = new QFile(path);
    if(!_mfile->open(QIODevice::WriteOnly)) {
        _mfile->close();
        delete _mfile;
        emit signalWaveInfo(QStringLiteral("打开合并音频文件异常!"));
        return;
    }

    _merge_exec = false;
    std::for_each(wave_list.constBegin(), wave_list.constEnd(), [this](const QString &item) {
        merge_wave_item(item);
    });

    if(_merge_exec) {
        uint32_t sz = _audio_size + _head_size;

        sz -= 8;
        memcpy(_wav_head + 4, &sz, 4);
        sz = sz + 8 - _head_size;
        memcpy(_wav_head + _head_size - 4, &sz, 4);
        _mfile->seek(0);
        _mfile->write(_wav_head, _head_size);

        emit signalWaveInfo(QStringLiteral("合并音频完成!\n"));
    }

    _mfile->close();
    delete _mfile;

    if(_merge_exec) {
        check_waveinfo(path);
    }
}
